//------------------------------------------------------------------------------
// <copyright file="XmlNode.cs" company="Microsoft">
//     
//      Copyright (c) 2006 Microsoft Corporation.  All rights reserved.
//     
//      The use and distribution terms for this software are contained in the file
//      named license.txt, which can be found in the root of this distribution.
//      By using this software in any fashion, you are agreeing to be bound by the
//      terms of this license.
//     
//      You must not remove this notice, or any other, from this software.
//     
// </copyright>                                                                
//------------------------------------------------------------------------------

namespace System.Xml {
    using System;
    using System.IO;
    using System.Collections;
    using System.Text;
    using System.Diagnostics;
    using System.Xml.Schema;
    using System.Xml.XPath;
    using MS.Internal.Xml.XPath;
    using System.Globalization;
    
    // Represents a single node in the document.
    [DebuggerDisplay("{new DebuggerDisplayXmlNodeProxy(this)}")]
    public abstract class XmlNode : ICloneable, IEnumerable, IXPathNavigable {
        internal XmlNode parentNode; //this pointer is reused to save the userdata information, need to prevent internal user access the pointer directly.

        internal XmlNode () {
        }
        
        internal XmlNode( XmlDocument doc ) {
            if ( doc == null )
                throw new ArgumentException(Res.GetString(Res.Xdom_Node_Null_Doc));
            this.parentNode = doc;
        }

        public virtual XPathNavigator CreateNavigator() {
            XmlDocument doc = OwnerDocument;
            Debug.Assert( doc != null );
            return doc.CreateNavigator( this );
        }

        // Selects the first node that matches the xpath expression
        public XmlNode SelectSingleNode( string xpath ) {
            XmlNodeList list = SelectNodes(xpath);
            //since SelectNodes could return null for certain node types
            if( ( list != null ) && ( list.Count > 0 ) )               
                return list[0];
            else
                return null;
        }

        // Selects the first node that matches the xpath expression and given namespace context.
        public XmlNode SelectSingleNode( string xpath, XmlNamespaceManager nsmgr ) {
            XPathNavigator xn = (this).CreateNavigator();
            //if the method is called on node types like DocType, Entity, XmlDeclaration,
            //the navigator returned is null. So just return null from here for those node types.
            if( xn == null )
                return null;            
            XPathExpression exp = xn.Compile(xpath);
            exp.SetContext(nsmgr);
            XmlNodeList list = new XPathNodeList( xn.Select(exp) );;
            if( list.Count > 0 )
                return list[0];
            else
                return null;
        }
                
        // Selects all nodes that match the xpath expression
        public XmlNodeList SelectNodes( string xpath ) {
            XPathNavigator n = (this).CreateNavigator();
            //if the method is called on node types like DocType, Entity, XmlDeclaration,
            //the navigator returned is null. So just return null from here for those node types.
            if( n == null )
                return null;
            return new XPathNodeList( n.Select(xpath) );
        }
        
        // Selects all nodes that match the xpath expression and given namespace context.
        public XmlNodeList SelectNodes( string xpath, XmlNamespaceManager nsmgr ) {
            XPathNavigator xn = (this).CreateNavigator();
            //if the method is called on node types like DocType, Entity, XmlDeclaration,
            //the navigator returned is null. So just return null from here for those node types.
            if( xn == null )
                return null;
            XPathExpression exp = xn.Compile(xpath);
            exp.SetContext(nsmgr);
            return new XPathNodeList( xn.Select(exp) );
        }

        // Gets the name of the node.
        public abstract string Name { 
            get; 
        }

        // Gets or sets the value of the node.
        public virtual string Value { 
            get { return null;}
            set { throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Res.GetString(Res.Xdom_Node_SetVal), NodeType.ToString()));}
        }

        // Gets the type of the current node.
        public abstract XmlNodeType NodeType {
            get;
        }

        // Gets the parent of this node (for nodes that can have parents).
        public virtual XmlNode ParentNode { 
            get {
                Debug.Assert(parentNode != null);

                if (parentNode.NodeType != XmlNodeType.Document) {
                    return parentNode;
                }

                // Linear lookup through the children of the document 
                XmlLinkedNode firstChild = parentNode.FirstChild as XmlLinkedNode;
                if (firstChild != null) {
                    XmlLinkedNode node = firstChild;
                    do {
                        if (node == this) {
                            return parentNode;
                        }
                        node = node.next;
                    }
                    while (node != null
                           && node != firstChild);
                }
                return null;
            }
        }

        // Gets all children of this node.
        public virtual XmlNodeList ChildNodes { 
            get { return new XmlChildNodes(this);}
        }

        // Gets the node immediately preceding this node.
        public virtual XmlNode PreviousSibling { 
            get { return null;}
        }

        // Gets the node immediately following this node.
        public virtual XmlNode NextSibling { 
            get { return null;} 
        }

        // Gets a XmlAttributeCollection containing the attributes
        // of this node.
        public virtual XmlAttributeCollection Attributes { 
            get { return null;} 
        }

        // Gets the XmlDocument that contains this node.
        public virtual XmlDocument OwnerDocument { 
            get { 
                Debug.Assert( parentNode != null );
                if ( parentNode.NodeType == XmlNodeType.Document)
                    return (XmlDocument)parentNode;
                return parentNode.OwnerDocument;
            } 
        }

        // Gets the first child of this node.
        public virtual XmlNode FirstChild { 
            get { 
                XmlLinkedNode linkedNode = LastNode;
                if (linkedNode != null)
                    return linkedNode.next;

                return null; 
            }
        }

        // Gets the last child of this node.
        public virtual XmlNode LastChild { 
            get { return LastNode;}
        }

        internal virtual bool IsContainer {
            get { return false;}
        }

        internal virtual XmlLinkedNode LastNode {
            get { return null;}
            set {}
        }

        internal bool AncestorNode(XmlNode node) {
            XmlNode n = this.ParentNode;

            while (n != null && n != this) {
                if (n == node)
                    return true;
                n = n.ParentNode;
            }

            return false;
        }

        //trace to the top to find out its parent node.
        internal bool IsConnected()
        {
            XmlNode parent = ParentNode;
            while (parent != null && !( parent.NodeType == XmlNodeType.Document ))
                parent = parent.ParentNode;           
            return parent != null;
        }

        // Inserts the specified node immediately before the specified reference node.
        public virtual XmlNode InsertBefore(XmlNode newChild, XmlNode refChild) {
            if (this == newChild || AncestorNode(newChild))
                throw new ArgumentException(Res.GetString(Res.Xdom_Node_Insert_Child));

            if (refChild == null)
                return AppendChild(newChild);

            if (!IsContainer)
                throw new InvalidOperationException(Res.GetString(Res.Xdom_Node_Insert_Contain));

            if (refChild.ParentNode != this)
                throw new ArgumentException(Res.GetString(Res.Xdom_Node_Insert_Path));

            if (newChild == refChild)
                return newChild;

            XmlDocument childDoc = newChild.OwnerDocument;
            XmlDocument thisDoc = OwnerDocument;
            if (childDoc != null && childDoc != thisDoc && childDoc != this)
                throw new ArgumentException(Res.GetString(Res.Xdom_Node_Insert_Context));

            if (!CanInsertBefore( newChild, refChild ))
                throw new InvalidOperationException(Res.GetString(Res.Xdom_Node_Insert_Location));

            if (newChild.ParentNode != null)
                newChild.ParentNode.RemoveChild( newChild );

            // special case for doc-fragment.
            if (newChild.NodeType == XmlNodeType.DocumentFragment) {
                XmlNode first = newChild.FirstChild;
                XmlNode node = first;
                if (node != null) {
                    newChild.RemoveChild( node );
                    InsertBefore( node, refChild );
                    // insert the rest of the children after this one.
                    InsertAfter( newChild, node );
                }
                return first;
            }

            if (!(newChild is XmlLinkedNode) || !IsValidChildType(newChild.NodeType))
                throw new InvalidOperationException(Res.GetString(Res.Xdom_Node_Insert_TypeConflict));

            XmlLinkedNode newNode = (XmlLinkedNode) newChild;
            XmlLinkedNode refNode = (XmlLinkedNode) refChild;

            string newChildValue = newChild.Value;
            XmlNodeChangedEventArgs args = GetEventArgs( newChild, newChild.ParentNode, this, newChildValue, newChildValue, XmlNodeChangedAction.Insert );

            if (args != null)
                BeforeEvent( args );

            if (refNode == FirstChild) {
                newNode.next = refNode; 
                LastNode.next = newNode;
                newNode.SetParent(this);

                if (newNode.IsText) {
                    if (refNode.IsText) {
                        NestTextNodes(newNode, refNode);
                    }
                }
            }
            else {
                XmlLinkedNode prevNode = (XmlLinkedNode) refNode.PreviousSibling;

                newNode.next = refNode;
                prevNode.next = newNode;
                newNode.SetParent(this);

                if (prevNode.IsText) {
                    if (newNode.IsText) {
                        NestTextNodes(prevNode, newNode);
                        if (refNode.IsText) {
                            NestTextNodes(newNode, refNode);
                        }
                    }
                    else {
                        if (refNode.IsText) {
                            UnnestTextNodes(prevNode, refNode);
                        }
                    }
                }
                else {
                    if (newNode.IsText) {
                        if (refNode.IsText) {
                            NestTextNodes(newNode, refNode);
                        }
                    }
                }
            }

            if (args != null)
                AfterEvent( args );

            return newNode;
        }

        // Inserts the specified node immediately after the specified reference node.
        public virtual XmlNode InsertAfter(XmlNode newChild, XmlNode refChild) {
            if (this == newChild || AncestorNode(newChild))
                throw new ArgumentException(Res.GetString(Res.Xdom_Node_Insert_Child));

            if (refChild == null)
                return PrependChild(newChild);

            if (!IsContainer)
                throw new InvalidOperationException(Res.GetString(Res.Xdom_Node_Insert_Contain));

            if (refChild.ParentNode != this)
                throw new ArgumentException(Res.GetString(Res.Xdom_Node_Insert_Path));

            if (newChild == refChild)
                return newChild;

            XmlDocument childDoc = newChild.OwnerDocument;
            XmlDocument thisDoc = OwnerDocument;
            if (childDoc != null && childDoc != thisDoc && childDoc != this)
                throw new ArgumentException(Res.GetString(Res.Xdom_Node_Insert_Context));

            if (!CanInsertAfter( newChild, refChild ))
                throw new InvalidOperationException(Res.GetString(Res.Xdom_Node_Insert_Location));

            if (newChild.ParentNode != null)
                newChild.ParentNode.RemoveChild( newChild );

            // special case for doc-fragment.
            if (newChild.NodeType == XmlNodeType.DocumentFragment) {
                XmlNode last = refChild;
                XmlNode first = newChild.FirstChild;
                XmlNode node = first;
                while (node != null) {
                    XmlNode next = node.NextSibling;
                    newChild.RemoveChild( node );
                    InsertAfter( node, last );
                    last = node;
                    node = next;
                }
                return first;
            }

            if (!(newChild is XmlLinkedNode) || !IsValidChildType(newChild.NodeType))
                throw new InvalidOperationException(Res.GetString(Res.Xdom_Node_Insert_TypeConflict));

            XmlLinkedNode newNode = (XmlLinkedNode) newChild;
            XmlLinkedNode refNode = (XmlLinkedNode) refChild;

            string newChildValue = newChild.Value;
            XmlNodeChangedEventArgs args = GetEventArgs( newChild, newChild.ParentNode, this, newChildValue, newChildValue, XmlNodeChangedAction.Insert );

            if (args != null)
                BeforeEvent( args );

            if (refNode == LastNode) {
                newNode.next = refNode.next;
                refNode.next = newNode;
                LastNode = newNode;
                newNode.SetParent(this);

                if (refNode.IsText) {
                    if (newNode.IsText) {
                        NestTextNodes(refNode, newNode);
                    }
                }
            }
            else {
                XmlLinkedNode nextNode = refNode.next;

                newNode.next = nextNode;
                refNode.next = newNode;
                newNode.SetParent(this);

                if (refNode.IsText) {
                    if (newNode.IsText) {
                        NestTextNodes(refNode, newNode);
                        if (nextNode.IsText) {
                            NestTextNodes(newNode, nextNode);
                        }
                    }
                    else {
                        if (nextNode.IsText) {
                            UnnestTextNodes(refNode, nextNode);
                        }
                    }
                }
                else {
                    if (newNode.IsText) {
                        if (nextNode.IsText) {
                            NestTextNodes(newNode, nextNode);
                        }
                    }
                }
            }


            if (args != null)
                AfterEvent( args );

            return newNode;
        }

        // Replaces the child node oldChild with newChild node.
        public virtual XmlNode ReplaceChild(XmlNode newChild, XmlNode oldChild) {
            XmlNode nextNode = oldChild.NextSibling;
            RemoveChild(oldChild);
            XmlNode node = InsertBefore( newChild, nextNode );
            return oldChild;
        }

        // Removes specified child node.
        public virtual XmlNode RemoveChild(XmlNode oldChild) {
            if (!IsContainer)
                throw new InvalidOperationException(Res.GetString(Res.Xdom_Node_Remove_Contain));

            if (oldChild.ParentNode != this)
                throw new ArgumentException(Res.GetString(Res.Xdom_Node_Remove_Child));

            XmlLinkedNode oldNode = (XmlLinkedNode) oldChild;

            string oldNodeValue = oldNode.Value;
            XmlNodeChangedEventArgs args = GetEventArgs( oldNode, this, null, oldNodeValue, oldNodeValue, XmlNodeChangedAction.Remove );

            if (args != null)
                BeforeEvent( args );

            XmlLinkedNode lastNode = LastNode;

            if (oldNode == FirstChild) {
                if (oldNode == lastNode) {
                    LastNode = null;
                    oldNode.next = null;
                    oldNode.SetParent( null );
                }
                else {
                    XmlLinkedNode nextNode = oldNode.next;

                    if (nextNode.IsText) {
                        if (oldNode.IsText) {
                            UnnestTextNodes(oldNode, nextNode);
                        }
                    }

                    lastNode.next = nextNode;
                    oldNode.next = null;
                    oldNode.SetParent( null );
                }
            }
            else {
                if (oldNode == lastNode) {
                    XmlLinkedNode prevNode = (XmlLinkedNode) oldNode.PreviousSibling;
                    prevNode.next = oldNode.next;
                    LastNode = prevNode;
                    oldNode.next = null;
                    oldNode.SetParent(null);
                }
                else {
                    XmlLinkedNode prevNode = (XmlLinkedNode) oldNode.PreviousSibling;
                    XmlLinkedNode nextNode = oldNode.next;

                    if (nextNode.IsText) {
                        if (prevNode.IsText) {
                            NestTextNodes(prevNode, nextNode);
                        }
                        else {
                            if (oldNode.IsText) {
                                UnnestTextNodes(oldNode, nextNode);
                            }
                        }
                    }

                    prevNode.next = nextNode;
                    oldNode.next = null;
                    oldNode.SetParent(null);
                }
            }

            if (args != null)
                AfterEvent( args );

            return oldChild;
        }

        // Adds the specified node to the beginning of the list of children of this node.
        public virtual XmlNode PrependChild(XmlNode newChild) {
            return InsertBefore(newChild, FirstChild);
        }

        // Adds the specified node to the end of the list of children of this node.
        public virtual XmlNode AppendChild(XmlNode newChild) {
            XmlDocument thisDoc = OwnerDocument;
            if ( thisDoc == null ) {
                thisDoc = this as XmlDocument;
            }
            if (!IsContainer)
                throw new InvalidOperationException(Res.GetString(Res.Xdom_Node_Insert_Contain));

            if (this == newChild || AncestorNode(newChild))
                throw new ArgumentException(Res.GetString(Res.Xdom_Node_Insert_Child));

            if (newChild.ParentNode != null)
                newChild.ParentNode.RemoveChild( newChild );

            XmlDocument childDoc = newChild.OwnerDocument;
            if (childDoc != null && childDoc != thisDoc && childDoc != this)
                throw new ArgumentException(Res.GetString(Res.Xdom_Node_Insert_Context));

            // special case for doc-fragment.
            if (newChild.NodeType == XmlNodeType.DocumentFragment) {
                XmlNode first = newChild.FirstChild;
                XmlNode node = first;
                while (node != null) {
                    XmlNode next = node.NextSibling;
                    newChild.RemoveChild( node );
                    AppendChild( node );
                    node = next;
                }
                return first;
            }

            if (!(newChild is XmlLinkedNode) || !IsValidChildType(newChild.NodeType))
                throw new InvalidOperationException(Res.GetString(Res.Xdom_Node_Insert_TypeConflict));


            if (!CanInsertAfter( newChild, LastChild ))
                throw new InvalidOperationException(Res.GetString(Res.Xdom_Node_Insert_Location));

            string newChildValue = newChild.Value;
            XmlNodeChangedEventArgs args = GetEventArgs( newChild, newChild.ParentNode, this, newChildValue, newChildValue, XmlNodeChangedAction.Insert );

            if (args != null)
                BeforeEvent( args );

            XmlLinkedNode refNode = LastNode;
            XmlLinkedNode newNode = (XmlLinkedNode) newChild;

            if (refNode == null) {
                newNode.next = newNode;
                LastNode = newNode;
                newNode.SetParent(this);
            }
            else {
                newNode.next = refNode.next;
                refNode.next = newNode;
                LastNode = newNode;
                newNode.SetParent(this);

                if (refNode.IsText) {
                    if (newNode.IsText) {
                        NestTextNodes(refNode, newNode);
                    }
                }
            }

            if (args != null)
                AfterEvent( args );

            return newNode;
        }

        //the function is provided only at Load time to speed up Load process
        internal virtual XmlNode AppendChildForLoad(XmlNode newChild, XmlDocument doc) {
            XmlNodeChangedEventArgs args = doc.GetInsertEventArgsForLoad( newChild, this );

            if (args != null)
                doc.BeforeEvent( args );

            XmlLinkedNode refNode = LastNode;
            XmlLinkedNode newNode = (XmlLinkedNode) newChild;

            if (refNode == null) {
                newNode.next = newNode;
                LastNode = newNode;
                newNode.SetParentForLoad(this);
            }
            else {
                newNode.next = refNode.next;
                refNode.next = newNode;
                LastNode = newNode;
                if (refNode.IsText
                    && newNode.IsText) {
                    NestTextNodes(refNode, newNode);
                }
                else {
                    newNode.SetParentForLoad(this);
                }
            }

            if (args != null)
                doc.AfterEvent( args );

            return newNode;
        }
        
        internal virtual bool IsValidChildType( XmlNodeType type ) {
            return false;
        }

        internal virtual bool CanInsertBefore( XmlNode newChild, XmlNode refChild ) {
            return true;
        }

        internal virtual bool CanInsertAfter( XmlNode newChild, XmlNode refChild ) {
            return true;
        }

        // Gets a value indicating whether this node has any child nodes.
        public virtual bool HasChildNodes { 
            get { return LastNode != null;}
        }

        // Creates a duplicate of this node.
        public abstract XmlNode CloneNode(bool deep);

        internal virtual void CopyChildren( XmlDocument doc, XmlNode container, bool deep ) {
            for (XmlNode child = container.FirstChild; child != null; child = child.NextSibling) {
                AppendChildForLoad( child.CloneNode(deep), doc );
            }
        }

        // DOM Level 2

        // Puts all XmlText nodes in the full depth of the sub-tree
        // underneath this XmlNode into a "normal" form where only
        // markup (e.g., tags, comments, processing instructions, CDATA sections,
        // and entity references) separates XmlText nodes, that is, there
        // are no adjacent XmlText nodes.
        public virtual void Normalize() {
            XmlNode firstChildTextLikeNode = null;
            StringBuilder sb = new StringBuilder();            
            for ( XmlNode crtChild = this.FirstChild; crtChild != null; ) {
                XmlNode nextChild = crtChild.NextSibling;
                switch ( crtChild.NodeType ) {
                    case XmlNodeType.Text:
                    case XmlNodeType.Whitespace: 
                    case XmlNodeType.SignificantWhitespace: {                        
                        sb.Append( crtChild.Value );
                        XmlNode winner = NormalizeWinner( firstChildTextLikeNode, crtChild );
                        if ( winner == firstChildTextLikeNode ) {
                            this.RemoveChild( crtChild );
                        } 
                        else {
                            if ( firstChildTextLikeNode != null )
                                this.RemoveChild( firstChildTextLikeNode );
                            firstChildTextLikeNode = crtChild;
                        }
                        break;
                    }
                    case XmlNodeType.Element: {
                        crtChild.Normalize();
                        goto default;
                    }
                    default : {
                        if ( firstChildTextLikeNode != null ) {
                            firstChildTextLikeNode.Value = sb.ToString();
                            firstChildTextLikeNode = null;
                        }
                        sb.Remove( 0, sb.Length );
			break;
                    }
                }
                crtChild = nextChild;
            }
            if ( firstChildTextLikeNode != null && sb.Length > 0 )
                firstChildTextLikeNode.Value = sb.ToString();
        }

        private XmlNode NormalizeWinner( XmlNode firstNode, XmlNode secondNode ) {
            //first node has the priority
            if ( firstNode == null )
                return secondNode;
            Debug.Assert( firstNode.NodeType == XmlNodeType.Text 
                        || firstNode.NodeType == XmlNodeType.SignificantWhitespace
                        || firstNode.NodeType == XmlNodeType.Whitespace
                        || secondNode.NodeType == XmlNodeType.Text 
                        || secondNode.NodeType == XmlNodeType.SignificantWhitespace
                        || secondNode.NodeType == XmlNodeType.Whitespace );
            if ( firstNode.NodeType == XmlNodeType.Text )
                return firstNode;
            if ( secondNode.NodeType == XmlNodeType.Text )
                return secondNode;
            if ( firstNode.NodeType == XmlNodeType.SignificantWhitespace )
                return firstNode;
            if ( secondNode.NodeType == XmlNodeType.SignificantWhitespace )
                return secondNode;
            if ( firstNode.NodeType == XmlNodeType.Whitespace )
                return firstNode;
            if ( secondNode.NodeType == XmlNodeType.Whitespace )
                return secondNode;
            Debug.Assert( true, "shouldn't have fall through here." );
            return null;
        }

        // Test if the DOM implementation implements a specific feature.
        public virtual bool Supports(string feature, string version) {
            if (String.Compare("XML", feature, StringComparison.OrdinalIgnoreCase) == 0) {
                if (version == null || version == "1.0" || version == "2.0")
                    return true;
            }
            return false;
        }

        // Gets the namespace URI of this node.
        public virtual string NamespaceURI { 
            get { return string.Empty;} 
        }

        // Gets or sets the namespace prefix of this node.
        public virtual string Prefix { 
            get { return string.Empty;} 
            set {} 
        }

        // Gets the name of the node without the namespace prefix.
        public abstract string LocalName { 
            get;
        }

        // Microsoft extensions

        // Gets a value indicating whether the node is read-only.
        public virtual bool IsReadOnly {
            get { 
                XmlDocument doc = OwnerDocument;
                return HasReadOnlyParent( this );
            }
        }

        internal static bool HasReadOnlyParent( XmlNode n ) {
            while (n != null) {
                switch (n.NodeType) {
                    case XmlNodeType.EntityReference:
                    case XmlNodeType.Entity:
                        return true;

                    case XmlNodeType.Attribute:
                    n = ((XmlAttribute)n).OwnerElement;
                        break;

                    default:
                    n = n.ParentNode;
                        break;
                }
            }
            return false;
        }

        // Creates a duplicate of this node.
        public virtual XmlNode Clone() {
            return this.CloneNode(true);
        }

        object ICloneable.Clone() {
            return this.CloneNode(true);
        }

        // Provides a simple ForEach-style iteration over the
        // collection of nodes in this XmlNamedNodeMap.
        IEnumerator IEnumerable.GetEnumerator() {
            return new XmlChildEnumerator(this);
        }

        public IEnumerator GetEnumerator() {
            return new XmlChildEnumerator(this);
        }

        private void AppendChildText( StringBuilder builder ) {
            for (XmlNode child = FirstChild; child != null; child = child.NextSibling) {
                if (child.FirstChild == null) {
                    if (child.NodeType == XmlNodeType.Text || child.NodeType == XmlNodeType.CDATA
                        || child.NodeType == XmlNodeType.Whitespace || child.NodeType == XmlNodeType.SignificantWhitespace)
                        builder.Append( child.InnerText );
                }
                else {
                    child.AppendChildText( builder );
                }
            }
        }

        // Gets or sets the concatenated values of the node and
        // all its children.
        public virtual string InnerText { 
            get {
                XmlNode fc = FirstChild;
                if (fc == null) {
                    return string.Empty;
                }
                if (fc.NextSibling == null) {
                    XmlNodeType nodeType = fc.NodeType;
                    switch (nodeType) {
                        case XmlNodeType.Text:
                        case XmlNodeType.CDATA:
                        case XmlNodeType.Whitespace:
                        case XmlNodeType.SignificantWhitespace:
                            return fc.Value;
                    }
                }
                StringBuilder builder = new StringBuilder();
                AppendChildText( builder );
                return builder.ToString();
            }

            set {
                XmlNode firstChild = FirstChild;
                if ( firstChild != null  //there is one child
                    && firstChild.NextSibling == null // and exactly one 
                    && firstChild.NodeType == XmlNodeType.Text )//which is a text node
                {
                    //this branch is for perf reason and event fired when TextNode.Value is changed
                    firstChild.Value = value;
                } 
                else {
                    RemoveAll();
                    AppendChild( OwnerDocument.CreateTextNode( value ) );
                }
            }
        }

        // Gets the markup representing this node and all its children.
        public virtual string OuterXml { 
            get {
                StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
                XmlDOMTextWriter xw = new XmlDOMTextWriter( sw );
                try {
                    WriteTo( xw );
                }
                finally {
                    xw.Close();
                }
                return sw.ToString();
            }
        }

        // Gets or sets the markup representing just the children of this node.
        public virtual string InnerXml {
            get { 
                StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
                XmlDOMTextWriter xw = new XmlDOMTextWriter( sw );
                try {
                    WriteContentTo( xw );
                }
                finally {
                    xw.Close();
                }
                return sw.ToString();
            }

            set { 
                throw new InvalidOperationException( Res.GetString(Res.Xdom_Set_InnerXml ) );
            }
        }

        public virtual IXmlSchemaInfo SchemaInfo {
            get {
                return XmlDocument.NotKnownSchemaInfo;
            }
        }

        public virtual String BaseURI {
            get {
                XmlNode curNode = this.ParentNode; //save one while loop since if going to here, the nodetype of this node can't be document, entity and entityref
                while ( curNode != null ) {
                    XmlNodeType nt = curNode.NodeType;
                    //EntityReference's children come from the dtd where they are defined.
                    //we need to investigate the same thing for entity's children if they are defined in an external dtd file.
                    if ( nt == XmlNodeType.EntityReference )
                        return ((XmlEntityReference)curNode).ChildBaseURI;
                    if ( nt == XmlNodeType.Document 
                        || nt == XmlNodeType.Entity 
                        || nt == XmlNodeType.Attribute )
                        return curNode.BaseURI;
                    curNode = curNode.ParentNode;
                }
                return String.Empty;
            }
        }

        // Saves the current node to the specified XmlWriter.
        public abstract void WriteTo(XmlWriter w);

        // Saves all the children of the node to the specified XmlWriter.
        public abstract void WriteContentTo(XmlWriter w);

        // Removes all the children and/or attributes
        // of the current node.
        public virtual void RemoveAll() {
            XmlNode child = FirstChild;
            XmlNode sibling = null;

            while (child != null) {
                sibling = child.NextSibling;
                RemoveChild( child );
                child = sibling;
            }
        }

        internal XmlDocument Document {
            get {
                if (NodeType == XmlNodeType.Document)
                    return (XmlDocument)this;
                return OwnerDocument;
            }
        }

        // Looks up the closest xmlns declaration for the given
        // prefix that is in scope for the current node and returns
        // the namespace URI in the declaration.
        public virtual string GetNamespaceOfPrefix(string prefix) {
            string namespaceName = GetNamespaceOfPrefixStrict(prefix);
            return namespaceName != null ? namespaceName : string.Empty;
        }

        internal string GetNamespaceOfPrefixStrict(string prefix) {
            XmlDocument doc = Document;
            if (doc != null) {
                prefix = doc.NameTable.Get(prefix);
                if (prefix == null)
                    return null;

                XmlNode node = this;
                while (node != null) {
                    if (node.NodeType == XmlNodeType.Element) {
                        XmlElement elem = (XmlElement)node;
                        if (elem.HasAttributes) {
                            XmlAttributeCollection attrs = elem.Attributes;
                            if (prefix.Length == 0) {
                                for (int iAttr = 0; iAttr < attrs.Count; iAttr++) {
                                    XmlAttribute attr = attrs[iAttr];
                                    if (attr.Prefix.Length == 0) {
                                        if (Ref.Equal(attr.LocalName, doc.strXmlns)) {
                                            return attr.Value; // found xmlns
                                        }
                                    }
                                }
                            }
                            else {
                                for (int iAttr = 0; iAttr < attrs.Count; iAttr++) {
                                    XmlAttribute attr = attrs[iAttr];
                                    if (Ref.Equal(attr.Prefix, doc.strXmlns)) {
                                        if (Ref.Equal(attr.LocalName, prefix)) {
                                            return attr.Value; // found xmlns:prefix
                                        }
                                    }
                                    else if (Ref.Equal(attr.Prefix, prefix)) {
                                        return attr.NamespaceURI; // found prefix:attr
                                    }
                                }
                            }
                        }
                        if (Ref.Equal(node.Prefix, prefix)) {
                            return node.NamespaceURI;
                        }
                        node = node.ParentNode;
                    }
                    else if (node.NodeType == XmlNodeType.Attribute) {
                        node = ((XmlAttribute)node).OwnerElement;
                    }
                    else {
                        node = node.ParentNode;
                    }
                }
                if (Ref.Equal(doc.strXml, prefix)) { // xmlns:xml
                    return doc.strReservedXml;
                }
                else if (Ref.Equal(doc.strXmlns, prefix)) { // xmlns:xmlns
                    return doc.strReservedXmlns;
                }
            }
            return null; 
        }

        // Looks up the closest xmlns declaration for the given namespace
        // URI that is in scope for the current node and returns
        // the prefix defined in that declaration.
        public virtual string GetPrefixOfNamespace(string namespaceURI) {
            string prefix = GetPrefixOfNamespaceStrict(namespaceURI);
            return prefix != null ? prefix : string.Empty;
        }

        internal string GetPrefixOfNamespaceStrict(string namespaceURI) {
            XmlDocument doc = Document;
            if (doc != null) {
                namespaceURI = doc.NameTable.Add(namespaceURI);

                XmlNode node = this;
                while (node != null) {
                    if (node.NodeType == XmlNodeType.Element) {
                        XmlElement elem = (XmlElement)node;
                        if (elem.HasAttributes) {
                            XmlAttributeCollection attrs = elem.Attributes;
                            for (int iAttr = 0; iAttr < attrs.Count; iAttr++) {
                                XmlAttribute attr = attrs[iAttr];
                                if (attr.Prefix.Length == 0) {
                                    if (Ref.Equal(attr.LocalName, doc.strXmlns)) {
                                        if (attr.Value == namespaceURI) {
                                            return string.Empty; // found xmlns="namespaceURI"
                                        }
                                    }
                                }
                                else if (Ref.Equal(attr.Prefix, doc.strXmlns)) {
                                    if (attr.Value == namespaceURI) {
                                        return attr.LocalName; // found xmlns:prefix="namespaceURI"
                                    }
                                }
                                else if (Ref.Equal(attr.NamespaceURI, namespaceURI)) {
                                    return attr.Prefix; // found prefix:attr
                                                        // with prefix bound to namespaceURI
                                }
                            }
                        }
                        if (Ref.Equal(node.NamespaceURI, namespaceURI)) {
                            return node.Prefix;
                        }
                        node = node.ParentNode;
                    }
                    else if (node.NodeType == XmlNodeType.Attribute) {
                        node = ((XmlAttribute)node).OwnerElement;
                    }
                    else {
                        node = node.ParentNode;
                    }
                }
                if (Ref.Equals(doc.strReservedXml, namespaceURI)) { // xmlns:xml
                    return doc.strXml;
                }
                else if (Ref.Equals(doc.strReservedXmlns, namespaceURI)) { // xmlns:xmlns
                    return doc.strXmlns;
                }
            }
            return null;
        }

        // Retrieves the first child element with the specified name.
        public virtual XmlElement this[string name]
        {
            get { 
                for (XmlNode n = FirstChild; n != null; n = n.NextSibling) {
                    if (n.NodeType == XmlNodeType.Element && n.Name == name)
                        return(XmlElement) n;
                }
                return null;
            }
        }

        // Retrieves the first child element with the specified LocalName and
        // NamespaceURI.
        public virtual XmlElement this[string localname, string ns]
        {
            get { 
                for (XmlNode n = FirstChild; n != null; n = n.NextSibling) {
                    if (n.NodeType == XmlNodeType.Element && n.LocalName == localname && n.NamespaceURI == ns)
                        return(XmlElement) n;
                }
                return null; 
            }
        }

        
        internal virtual void SetParent( XmlNode node ) {
            if (node == null) {
                this.parentNode = OwnerDocument;
            }
            else {
                this.parentNode = node;
            }
        }

        internal virtual void SetParentForLoad( XmlNode node ) {
            this.parentNode = node;
        }

        internal static void SplitName( string name, out string prefix, out string localName ) {
            int colonPos = name.IndexOf(':'); // ordinal compare
            if (-1 == colonPos || 0 == colonPos || name.Length-1 == colonPos) {
                prefix = string.Empty;
                localName = name;
            }
            else {
                prefix = name.Substring(0, colonPos);
                localName = name.Substring(colonPos+1);
            }
        }

        internal virtual XmlNode FindChild( XmlNodeType type ) {
            for (XmlNode child = FirstChild; child != null; child = child.NextSibling) {
                if (child.NodeType == type) {
                    return child;
                }
            }
            return null;
        }

        internal virtual XmlNodeChangedEventArgs GetEventArgs( XmlNode node, XmlNode oldParent, XmlNode newParent, string oldValue, string newValue, XmlNodeChangedAction action  ) {
            XmlDocument doc = OwnerDocument;
            if (doc != null) {
                if ( ! doc.IsLoading ) {
                    if ( ( (newParent != null && newParent.IsReadOnly) || ( oldParent != null && oldParent.IsReadOnly ) ) )
                        throw new InvalidOperationException( Res.GetString(Res.Xdom_Node_Modify_ReadOnly));
                }
                return doc.GetEventArgs( node, oldParent, newParent, oldValue, newValue, action );
            }
            return null;
        }
        
        internal virtual void BeforeEvent( XmlNodeChangedEventArgs args ) {
            if (args != null)
                OwnerDocument.BeforeEvent( args );
        }

        internal virtual void AfterEvent( XmlNodeChangedEventArgs args ) {
            if (args != null)
                OwnerDocument.AfterEvent( args );
        }

        internal virtual XmlSpace XmlSpace {
            get {
                XmlNode node = this;
                XmlElement elem = null;
                do {
                    elem = node as XmlElement;
                    if ( elem != null && elem.HasAttribute( "xml:space" ) ) {
                        String xmlSpaceVal = elem.GetAttribute( "xml:space" );
                        if ( 0 == String.Compare( xmlSpaceVal, "default", StringComparison.OrdinalIgnoreCase ) )
                            return XmlSpace.Default;
                        else if ( 0 == String.Compare( xmlSpaceVal, "preserve", StringComparison.OrdinalIgnoreCase ) )
                            return XmlSpace.Preserve;
                        //should we throw exception if value is otherwise?
                    }
                    node = node.ParentNode;
                } while ( node != null );
                return XmlSpace.None;
            }
        }

        internal virtual String XmlLang {
            get {
                XmlNode node = this;
                XmlElement elem = null;
                do {
                    elem = node as XmlElement;
                    if ( elem != null ) {
                        if ( elem.HasAttribute( "xml:lang" ) )
                            return elem.GetAttribute( "xml:lang" );
                    }
                    node = node.ParentNode;
                } while ( node != null );
                return String.Empty;
            }
        }

        internal virtual XPathNodeType XPNodeType { 
            get { 
                return (XPathNodeType)(-1); 
            } 
        }

        internal virtual string XPLocalName { 
            get { 
                return string.Empty; 
            } 
        }

        internal virtual string GetXPAttribute(string localName, string namespaceURI) {
            return String.Empty;
        }

        internal virtual bool IsText {
            get {
                return false;
            }
        }

        internal virtual XmlNode PreviousText { 
            get { 
                return null;
            }
        }

        internal static void NestTextNodes(XmlNode prevNode, XmlNode nextNode) {
            Debug.Assert(prevNode.IsText);
            Debug.Assert(nextNode.IsText);

            nextNode.parentNode = prevNode; 
        }

        internal static void UnnestTextNodes(XmlNode prevNode, XmlNode nextNode) {
            Debug.Assert(prevNode.IsText);
            Debug.Assert(nextNode.IsText);

            nextNode.parentNode = prevNode.ParentNode; 
        }
    }

    internal struct DebuggerDisplayXmlNodeProxy {
        private XmlNode node;

        public DebuggerDisplayXmlNodeProxy(XmlNode node) {
            this.node = node;
        }

        public override string ToString() {
            XmlNodeType nodeType = node.NodeType;
            string result = nodeType.ToString();
            switch (nodeType) {
                case XmlNodeType.Element:
                case XmlNodeType.EntityReference:
                    result += ", Name=\"" + node.Name + "\"";
                    break;
                case XmlNodeType.Attribute:
                case XmlNodeType.ProcessingInstruction:
                    result += ", Name=\"" + node.Name + "\", Value=\"" + XmlConvert.EscapeValueForDebuggerDisplay(node.Value) + "\"";
                    break;
                case XmlNodeType.Text:
                case XmlNodeType.CDATA:
                case XmlNodeType.Comment:
                case XmlNodeType.Whitespace:
                case XmlNodeType.SignificantWhitespace:
                case XmlNodeType.XmlDeclaration:
                    result += ", Value=\"" + XmlConvert.EscapeValueForDebuggerDisplay(node.Value) + "\"";
                    break;
                case XmlNodeType.DocumentType:
                    XmlDocumentType documentType = (XmlDocumentType)node;
                    result += ", Name=\"" + documentType.Name + "\", SYSTEM=\"" + documentType.SystemId + "\", PUBLIC=\"" + documentType.PublicId + "\", Value=\"" + XmlConvert.EscapeValueForDebuggerDisplay(documentType.InternalSubset) + "\"";
                    break;
                default:
                    break;
            }
            return result; 
        }
    }
}
